37

请解释事件代理 (event delegation)

事件委托技术能让你避免对特定的每个节点添加事件监听器;相反,事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。

优点:

  • 性能得到了优化(需要创建的以及驻留在内存中的事件处理器少了)
  • 动态添加的元素也能绑定事件了

this 值?

this 永远指向函数运行时所在的对象,而不是函数被创建时所在的对象。
this值在四种情况下:

  • 全局调用函数时,在ES5中为指向window对象,但在ES6中this的值为undefined
  • 对象调用方法时,this的值为指向该对象
  • 构造函数创建对象时,this指向新创建的对象
  • call/apply/bind 方法时this值被更改为它们三的第一个参数

请解释原型继承 (prototypal inheritance) 的原理?

原型继承的基础是原型链查找。

原型链查找基本概念:
每一个函数 F 都有一个原型对象(prototype)F.prototype
每一个函数都可以通过 new 关键字化身成为一个类构造函数,new F 会产生一个对象 O
在调用对象的某个属性或者方法,比如 http://O.xxx 的时候,会首先查找对象自身是否有这个方法或者属性,如果没找到就会去对象的构造函数的原型对象中查找(注意有两个定语),也就是查找 O 的构造函数 F 的原型对象 http://F.prototype.xxx
F.prototype 也是一个对象,查找 http://F.prototype.xxx 的时候会重复第 3 步的过程

请解释为什么接下来这段代码不是 IIFE (立即调用的函数表达式):function foo(){ }();要做哪些改动使它变成 IIFE?

 这里只是声明一个叫foo的function,直接用()执行这样是不成功的,想要变成IIFE就要把声明变成表达式,就可以立即执行了,可以这样(function foo(){})()或者(function foo(){}()),这就是用括号把定义强转成表达式,当然还有其他方法,关键就是声明不可以执行,表达式才可以执行。

描述以下变量的区别:null,undefined 或 undeclared? 该如何检测它们?

undefined:未定义,在变量没有赋值的时候的值即为undefined。“缺少值”,就是此处应该有一个值,但是还没有定义。
underclared:即为被污染的命名,访问没有被声明的变量,会抛出异常,终止执行。尝试访问一个undeclared的变量,浏览器会报错,JS执行会中断。
null:是一个空的对象引用。“没有对象”,即该处不应该有值

区别:
undefined和null在if语句中,都会被自动转为false,相等运算符甚至直接报告两者相等。typeof undefined会返回undefined ,而typeof null 总返回 object(typeof有六种可能:“number”、“string”、“boolean”、“object”、“function”、“undefined”)

false == undefined;//false
false == null;//false
null == undefined;//true

该如何检测它们?

var obj;
obj ===undefined; //检测undfined 方法一
typeof obj === ‘undefined’;//检测undefined方法2
obj = null;
obj === null;//来检测null
typeof null;//‘object’

什么是闭包 (closure),如何使用它,为什么要使用它?

定义:闭包就是可以读取到其他函数内部变量的函数。
闭包的用途:

  • 可以读取函数内部的变量。(外界无法访问函数的内部的私有方法和变量,只能通过提供的接口访问)
  • 让变量的值始终保持在内存中。
  • 可以避免污染全局变量,实现私有方法或者变量等

注意:

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

请举出一个匿名函数的典型用例?

匿名函数可以用作回调函数执行,可以防止全局变量污染。
在 JS 框架中常使用匿名函数来避免全局变量的污染。
$.(“input”).each(function(e){this.val(‘OK’)});
(function(){})();
$(document).ready(function(){ });
$(function() {})

请指出 JavaScript 宿主对象 (host objects) 和原生对象 (native objects) 的区别?

原生对象:独立于宿主环境的 ECMAScript 实现提供的对象。为array obj regexp date function等可以new实例化的对象。

内置对象:为gload Math 等,开发者不必明确实例化内置对象,它已被实例化了。类似于isNaN()、parseInt()和parseFloat()方法等,看起来都是函数,而实际上,它们都是Global对象的方法。具体可以参考 JavaScript 全局对象

宿主对象:即由 ECMAScript 实现的宿主环境(操作系统和浏览器)提供的对象。所有的BOM和DOM对象都是宿主对象。因为其对于不同的“宿主”环境所展示的内容不同(这就是兼容性和特性检测的缘由)。ECMAScript官方未定义的对象都属于宿主对象。

请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?

第一个为函数声明,第二个将函数person()返回值赋值给person,第三个通过Person()的构造器创建了一个对象让person变量引用该对象;

改变函数内部this指针的指向函数(bind,apply,call的区别)

  • 通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。
  • 通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。
    // apply 实现bind
    Function.prototype.bind = function (scope) {
        var fn = this;
        return function () {
            return fn.apply(scope);//使用call效果一样
        };
    }  

请指出浏览器特性检测,特性推断和浏览器 UA 字符串嗅探的区别?

检测浏览器的特殊名称和版本(用户代理检测)即浏览器UA字符串嗅探。浏览器嗅探技术可以快捷的将代码进行分支,以便针对不同的浏览器应用不同的指令;针对特定浏览器的特定版本,超出范围之外都是不可靠的

请尽可能详尽的解释 Ajax 的工作原理?

使用 Ajax 都有哪些优劣?

优势:可以刷新局部页面,而不用整体页面都刷新
缺点:用户禁用javascript的情况

请解释 JSONP 的工作原理,以及它为什么不是真正的 Ajax。

工作原理:JSONP动态创建script标签,回调函数。Ajax是页面无刷新请求数据操作,动态添加一个&ltscript>标签,而script标签的src属性是没有跨域的限制的。这样说来,这种跨域方式其实与ajax XmlHttpRequest协议无关了。
当GET请求从被调用页面返回时,可以返回一段JavaScript代码,这段代码会自动调用主页面中的一个callback函数。

优点:不受同源策略的影响,它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果   
缺点:只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

请解释变量声明提升 (hoisting)

在JavaScript代码运行之前其实是有一个编译阶段的。编译之后才是从上到下,一行一行解释执行。变量提升就发生在编译阶段,它把变量和函数的声明提升至作用域的顶端。(编译阶段的工作之一就是将变量与其作用域进行关联)。

变量提升需要注意:

  • 在js中用var ,function声明的变量都将被提到函数的最顶部。(但是不会初始化
  • 函数声明的优先级大于变量声明的优先级(function > var
  • 在函数内部变量提升的优先级会小于函数参数(函数参数 > 函数内部变量提升
  • 对于函数声明来说,如果定义了相同的函数变量声明,后定义的声明会覆盖掉先前的声明

请描述事件冒泡机制 (event bubbling)

从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被依次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播

“attribute” 和 “property” 的区别是什么?

DOM元素的attribute和property两者是不同的东西。attribute翻译为“特性”,property翻译为“属性”。

attribute是一个特性节点,每个DOM元素都有一个对应的attributes属性来存放所有的attribute节点,attributes是一个类数组的容器,说得准确点就是NameNodeMap,不继承于Array.prototype,不能直接调用Array的方法。attributes的每个数字索引以名值对(name=”value”)的形式存放了一个attribute节点。

property就是一个属性,如果把DOM元素看成是一个普通的Object对象,那么property就是一个以名值对(name=”value”)的形式存放在Object中的属性。要添加和删除property和普通的对象类似。

很多attribute节点还有一个相对应的property属性,比如上面的div元素的id和class既是attribute,也有对应的property,不管使用哪种方法都可以访问和修改。

总之,attribute节点都是在HTML代码中可见的,而property只是一个普通的名值对属性

为什么扩展 JavaScript 内置对象不是好的做法?

因为你不知道哪一天浏览器或javascript本身就会实现这个方法,而且和你扩展的实现有不一致的表现。到时候你的javascript代码可能已经在无数个页面中执行了数年,而浏览器的实现导致所有使用扩展原型的代码都崩溃了。

需要给Array原型添加一个distinct的方法,最好检查是否存在同名的方法,避免自定义方法覆盖原生方法:

Arrray.prototype.distinct = Arrray.prototype.distinct || function(){/…../}

请指出 document load 和 document DOMContentLoaded 两个事件的区别。

ready 表示文档的 DOM 已经加载完成(不包含图片、视频等资源);load 表示整个网页加载完成。可以看出,ready 事件发生在 load 事件之前。

== 和 === 有什么不同?

如果两边的操作数具有一致的类型且拥有相同的值时,=== 返回 true,!== 返回 false。

请解释 JavaScript 的同源策略 (same-origin policy)。

同源策略限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式。

同源策略出于安全,不允许源 A 的脚本读取(read)源 B 的资源的内容,但却允许执行(execute)源 B 的资源。这个概念也有些拗口。简单说,有一个页面调用了 Google CDN 提供的 jQuery,以及其它 CDN 上的 Bootstrap JS、CSS 代码,虽然它们与我的博客不同源,但我可以用它们来操作这个页面,并应用样式,这是执行的概念。

什么是三元表达式 (Ternary expression)?“三元 (Ternary)” 表示什么意思?

一个运算符如果有一个操作数,为一元运算符,两个为二元,三个为三元运算符,三元表达式则为一个三元运算表达式!

什么是 “use strict”; ? 使用它的好处和坏处分别是什么?

ECMAScript5中引入的严格模式,通过让JavaScript运行环境对一些开发过程中最常见和不易发现的错误做出和当前不同的处理,来让开发者拥有一个”更好”的JavaScript语言。

好处:

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。

好处具体体现:

  • 去除WITH关键词
  • 防止意外为全局变量赋值
  • 函数中的THIS不再默认指向全局
  • 防止重名
  • 安全的 EVAL()
  • 对只读属性修改时抛出异常

坏处:同样的代码,在“严格模式”中,可能会有不一样的运行结果;一些在“正常模式”下可以运行的语句,在“严格模式”下将不能运行

总结:启用JavaScript严格模式,它能帮你发现代码中未曾注意到的错误。不要在全局环境中启用,但你能尽量多的使用IIFE(立即执行函数表达式)来把严格模式作用到多个函数范围内。一开始,你会遇到之前未曾碰到过的错误提示,这是正常的。当启用严格模式后,请确保在支持的浏览器中做了测试,以发现新的潜在问题。一定不要仅仅在代码中添加一行”use strict”就假定余下的代码能正常工作。

为何通常会认为保留网站现有的全局作用域 (global scope) 不去改变它,是较好的选择?

它的意思是: 尽量少在全局作用域定义变量。

目的:减少名称冲突 利于模块化

为何你会使用 load 之类的事件 (event)?此事件有缺点吗?你是否知道其他替代品,以及为何使用它们?

要等到等页面完全加载后(所有图像、javascript文件、CSS等外部文件)。替代:把script标签放到最后面。

请解释什么是单页应用 (single page app), 以及如何使其对搜索引擎友好 (SEO-friendly)。

单页应用是一种特殊的web应用,它将所有的活动局限于一个web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript 和 CSS。

优点:

  • 用户体验:对于内容的改动不需要加载整个页面
  • 高效:服务器压力很小,消耗更少的带宽,能够与面向服务的架构更好地结合。
  • 经典MVC开发模式,前后端各负其责。
  • 一套Server API,多端使用(web、移动APP等)
  • 重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交

缺点:

  • 不利于SEO:解决方案也有一些:H5pushState,通过浏览器历史记录让搜索引擎抓取;url中#!
    复杂的单页架构页面,对Google来说抓取比较困难,于是给开发者制定一个规范:
    1)、网站提交sitemap给Google;
    2)、Google发现URL里有#!符号,例如example.com/#!/detail/1,于是Google开始抓取example.com/?_escaped_fragment_=/detail/1;_escaped_fragment_这个参数是Google指定的命名,如果开发者希望把网站内容提交给Google,就必须通过这个参数生成静态页面。
  • 首屏渲染速度慢

使用一种可以编译成 JavaScript 的语言来写 JavaScript 代码有哪些优缺点?

以Typescript为例子:
typescript是javascript的强类型版本,在编译期去掉类型和特有语法,生成纯粹的javascript代码。TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。

优点:

  • 静态类型检查
  • IDE 智能提示 (编译阶段即可发现类型不匹配的错误)
  • 代码重构
  • 可读性

缺点:

  • 不指定类型就写不了程序,类型只是辅助信息,并不是程序的本之后
  • 灵活性问题

你使用哪些工具和技术来调试 JavaScript 代码?

  • alert
  • console.log
  • 断点调试(这三种调试方式都是打断点)
  • js断点调试
  • source断点调试
  • Debugger断点(具体的说就是通过在代码中添加”debugger;”语句,当代码执行到该语句的时候就会自动断点。)
  • DOM断点调试
  • 当节点内部子节点变化时断点
  • 当节点属性发生变化时断点
  • 当节点被移除时断点

请解释可变 (mutable) 和不变 (immutable) 对象的区别?

javascript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此—-改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。实际上,javascript是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。

区别:

  • 可变性:对象和原始值不同,首先,它们是可变的–它们的值是可修改的
  • 值的比较:对象的比较并非值的比较:即使两个对象包含同样的属性及相同的值,它们也是不相等的。各个索引元素相等的两个数组也不相等。

不变性 (immutability) 有哪些优缺点?

优点:
*因为不能修改一个不变对象的状态,所以可以避免由此引起的不必要的程序错误;一个不变的对象要比一个可变的对象更加容易维护。

  • 因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样可以省掉处理同步化的开销。一个不变对象可以自由地被不同的客户端共享。

缺点:

  • 一旦需要修改一个不变对象的状态,就只好创建一个新的同类对象。在需要频繁修改不变对象的环境里,会有大量的不变对象作为中间结果被创建出来,这是一种资源上的浪费。

如何用你自己的代码来实现不变性 (immutability)?

可以使用const 修饰变量不可变

你会使用怎样的语言结构来遍历对象属性 (object properties) 和数组内容?

请解释同步 (synchronous) 和异步 (asynchronous) 函数的区别。

同步式:当计算机调度线程进行I/O操作命令后,由于文件的读写或者网络通信需要较长的操作时间,操作系统为了充分利用cpu,此时会暂停到当前的I/O线程对CPU的控制(故又称同步式为阻塞式I/O),把cup资源然给其他的线程资源,当I/O线程完成了操作时,此时操作系统会恢复此时的I/O线程,从而当前I/O线程重新获得了cup的的控制权,继续完成其他操作。

异步式:异步式IO又称非阻塞式I/O,异步式与同步式不同的是,当线程进行IO操作时,操作系统并不是暂停当前的线程操作,而是执行完I/O指令后,操作系统继续让当前线程执行下一条指令,当I/O操作完成后,会通过事件(event)通知I/O线程,而线程在接收到通知后,会处理响应事件。

什么是事件循环 (event loop)?

主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

请问调用栈 (call stack) 和任务队列 (task queue) 的区别是什么?

所谓“回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当异步任务从“任务队列”回到执行栈,回调函数就会执行。

“任务队列”是一个先进先出的数据结构,排在前面的事件,优先返回主线程。主线程的读取过程基本上是自动的,只要执行栈一清空,“任务队列”上第一位的事件就自动返回主线程。但是,由于存在后文提到的“定时器”功能,主线程要检查一下执行时间,某些事件必须要在规定的时间返回主线程。

解释 function foo() {} 与 var foo = function() {} 用法的区别?

第一个未函数声明,第二个为函数定义表达式(函数定义表达式foo,变量声明提前单赋值并未提前)

封装JavaScript源文件的全部内容到一个函数块有什么意义及理由?

这是一个越来越普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)采用。这种技术创建了一个围绕文件全部内容的闭包,也许是最重要的是,创建了一个私有的命名空间,从而有助于避免不同JavaScript模块和库之间潜在的名称冲突。
这种技术的另一个特点是,允许一个易于引用的(假设更短的)别名用于全局变量。这通常用于,例如,jQuery插件中。jQuery允许你使用jQuery.noConflict(),来禁用 $ 引用到jQuery命名空间。在完成这项工作之后,你的代码仍然可以使用$ 利用这种闭包技术,如下所示:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

在JavaScript源文件的开头包含 use strict 有什么意义和好处?

use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常。通常而言,这是一个很好的做法。

严格模式的一些主要优点包括:

  • 使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。
  • 防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。
  • 消除 this 强制。如果没有严格模式,引用null或未定义的值到 this 值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用 null或未定义的 this 值会抛出错误。
  • 不允许重复的属性名称或参数值。当检测到对象(例如,var object = {foo: "bar", foo: "baz"};)中重复命名的属性,或检测到函数中(例如,function foo(val1, val2, val1){})重复命名的参数时,严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。
  • 使eval() 更安全。在严格模式和非严格模式下,eval() 的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在 eval() 语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。
  • 在 delete使用无效时抛出错误。delete操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。

NaN 是什么?它的类型是什么?你如何可靠地测试一个值是否等于 NaN ?

NaN 属性代表一个“不是数字”的值。这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字(例如, "abc" / 4),要么是因为运算的结果非数字(例如,除数为零)。

NaN的特点:

  • NaN的类型为Number: console.log(typeof NaN === "Number"); // logs "true"
  • NaN 和任何东西比较——甚至是它自己本身!——结果是false:console.log(NaN === NaN); // logs "false"

测试数字为NaN的方法:

  • 使用内置函数iSNaN(半可靠)
  • value !== value,如果值等于NaN,只会产生true
  • ES6提供: Number.isNaN() 比老的isNaN更可靠

讨论写函数 isInteger(x) 的可能方法,用于确定x是否是整数

ECMAScript 6 之前没有提供类似 Number.isInteger 的方法。在ECMAScript规格说明中,整数只概念上存在:即,数字值总是存储为浮点值。
方法:

  • 最简单又最干净的ECMAScript6之前的解决方法(同时也非常稳健地返回 false ,即使一个非数字的值,如字符串或 null ,被传递给函数)如:function isInteger(x) { return (x^0) === x; }
  • function isInteger(x) { return Math.round(x) === x; } (不如第一个优雅)
  • function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0);
  • function isInteger(x) { return parseInt(x, 10) === x; }

虽然这个以 parseInt函数为基础的方法在 x 取许多值时都能工作良好,但一旦 x 取值相当大的时候,就会无法正常工作。问题在于 parseInt() 在解析数字之前强制其第一个参数到字符串。因此,一旦数目变得足够大,它的字符串就会表达为指数形式(例如, 1e+21)。因此,parseInt() 函数就会去解析 1e+21,但当到达 e字符串的时候,就会停止解析,因此只会返回值 1。注意:

String(1000000000000000000000)'1e+21'> parseInt(1000000000000000000000, 10)1> parseInt(1000000000000000000000, 10) === 1000000000000000000000false

JavaScript中的“闭包”是什么?请举一个例子

闭包是一个可以访问外部(封闭)函数作用域链中的变量的内部函数。
闭包可以访问三种范围中的变量:这三个范围具体为:

  • 自己范围内的变量
  • 封闭函数范围内的变量
  • 全局变量。

下面是一个简单的例子:

var globalVar = "xyz";
(function outerFunc(outerArg) { 
    var outerVar = 'a';
    (function innerFunc(innerArg) { 
        var innerVar = 'b'; 
        console.log( "outerArg = " + outerArg + "\n" 
        + "innerArg = " + innerArg + "\n" 
        + "outerVar = " + outerVar + "\n" 
        + "innerVar = " + innerVar + "\n" 
        + "globalVar = " + globalVar);
    })(456);   
})(123);

在上面的例子中,来自于 innerFunc, outerFunc和全局命名空间的变量都在 innerFunc的范围内。因此,上面的代码将输出如下:

outerArg = 123innerArg = 456outerVar = ainnerVar = bglobalVar = xyz

对象的深拷贝?ES6、ES5

浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用
深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

深拷贝对象(不讨论数组的情况):

  • 循环遍历对象的属性赋值
  • ES6的Object.assign: (用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target)
  • ES6扩展运算符:扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中
  • JSON.parse(JSON.stringify(obj)): 上面三种方法不能拷贝所有层级,该方法可以拷贝对象所有层级
  • valueOf 如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值
  • 手动些递归(-_-!)

参考:https://www.cnblogs.com/pengh...

跨域?

同源指的是:URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
同源策略:浏览器的同源策略,限制了来自不同源的"document"或脚本,对当前"document"读取或设置某些属性(但是允许当前源执行其他源的脚本)

前端跨域实现方式:

  • JSONP:<script> 标签是不受同源策略的限制的,它可以载入任意地方的 JavaScript 文件,而并不要求同源。
  • 修改document.domain: 浏览器脚本修改document.domain。域名只允许往上升,例如a.git.com到git.com,这样脚本就可以访问git.com上的资源了。还有种用法是假设浏览器访问了a.git.com和b.git.com,如果它们的脚本都把域名设为git.com,那么浏览器本地的资源可以共享。
  • window.postMessage:Html5的新方法,两个域下的脚本可以跨window通信。
  • 利用表单:浏览器里不禁止表单跨域,所以可以用Javascript + iframe + 表单实现跨域调用

服务端跨域实现方式:

  • 反向代理服务器: 将你的服务器配置成 需要跨域获取的资源的 反向代理服务器。也就是说,将其他域名的资源映射到你自己的域名之下,这样浏览器就认为他们是同源的。
  • Cross-Origin Resource Sharing: 是 W3C 推出的一种跨站资源获取的机制。服务器 在响应头中设置相应的选项,浏览器如果支持这种方法的话就会将这种跨站资源请求视为合法,进而获取资源。

谈谈cookies、sessionStorage、localStorage之间的区别?

主要从四个方面来回答:

  • 作用域
  • 大小
  • 生存周期
  • 存储位置 sessionstorage和localstorage都是html5提供的客户端存储方式(都保存在客户端)

区别:

  • cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递;cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。
  • 存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  • 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
  • 作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便。

原型 原型链 作用域 作用域链?

原型:在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)
原型链:JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法(这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身)。

作用域:作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

作用域有:

  • 全局作用域
  • 函数作用域
  • eval 作用域
  • 块级作用域(es6 let const)

作用域链: 通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链

JavaScript ( JS ) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言.JavaScript引擎实际上在执行代码前仅几微秒就编译了代码。
javaScript的执行分为:解释和执行两个阶段
解释阶段:

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段:

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

作用域和执行上下文之间最大的区别是:执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变

javascript基本数据类型有哪些?

javascript中有5中数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String,还有一种复杂数据类型——object,object本质是由一组键值对组成的。

new 对象时发生了什么?

  1. 初始化一个空对象
  2. 绑定 this
  3. 执行构造函数
  4. 返回这个对象(不需要return来返回)

form 表单 name属性的作用

name 属性规定表单的名称。
form 元素的 name 属性提供了一种在脚本中引用表单的方法。

数组去重方法有哪些?

方法:

  • 双层循环去重(新数组保存,遍历新数组查找有无已重复元素)
  • 排序后相邻去除法
/*
* 给传入数组排序,排序后相同值相邻,
* 然后遍历时,新数组只加入不与前一值重复的值。
* 会打乱原来数组的顺序
* */
function uniq(array){
    array.sort();
    var temp=[array[0]];
    for(var i = 1; i < array.length; i++){
        if( array[i] !== temp[temp.length-1]){
            temp.push(array[i]);
        }
    }
    return temp;
}

var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
console.log(uniq(aa));

备注:该方法有问题 2 ‘2’ 2 排序后按照下一元素和前一元素值不同,会出现数字2 字符2 数字2 

换一种方法


function uniq(array){
    array.sort();
    var temp=[array[0]];
    array.map(item => {
        if(!temp.find(t => t === item)){
            temp.push(item)
        }
    })
    return temp;
}

var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
console.log(uniq(aa));
  • 数组下标法
/*
*
* 还是得调用“indexOf”性能跟方法1差不多,
* 实现思路:如果当前数组的第i项在当前数组中第一次出现的位置不是i,
* 那么表示第i项是重复的,忽略掉。否则存入结果数组。
* */
function uniq(array){
    var temp = [];
    for(var i = 0; i < array.length; i++) {
        //如果当前数组的第i项在当前数组中第一次出现的位置是i,才存入数组;否则代表是重复的
        if(array.indexOf(array[i]) == i){
            temp.push(array[i])
        }
    }
    return temp;
}

var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
console.log(uniq(aa));
  • 优化遍历数组法
    // 思路:获取没重复的最右一值放入新数组
    /*
    * 推荐的方法
    *
    * 方法的实现代码相当酷炫,
    * 实现思路:获取没重复的最右一值放入新数组。
    * (检测到有重复值时终止当前循环同时进入顶层循环的下一轮判断)*/
    function uniq(array){
        var temp = [];
        var index = [];
        var l = array.length;
        for(var i = 0; i < l; i++) {
            for(var j = i + 1; j < l; j++){
                if (array[i] === array[j]){
                    i++;
                    j = i;
                }
            }
            temp.push(array[i]);
            index.push(i);
        }
        console.log(index);
        return temp;
    }
    
    var aa = [1,2,2,3,5,3,6,5];
    console.log(uniq(aa));
    
  • 对象键值法去重
    /*
    * 速度最快, 占空间最多(空间换时间)
    *
    * 该方法执行的速度比其他任何方法都快, 就是占用的内存大一些。
    * 现思路:新建一js对象以及新数组,遍历传入数组时,判断值是否为js对象的键,
    * 不是的话给对象新增该键并放入新数组。
    * 注意点:判断是否为js对象键时,会自动对传入的键执行“toString()”,
    * 不同的键可能会被误认为一样,例如n[val]-- n[1]、n["1"];
    * 解决上述问题还是得调用“indexOf”。*/
    function uniq(array){
        var temp = {}, r = [], len = array.length, val, type;
        for (var i = 0; i < len; i++) {
            val = array[i];
            type = typeof val;
            if (!temp[val]) {
                temp[val] = [type];
                r.push(val);
            } else if (temp[val].indexOf(type) < 0) {
                temp[val].push(type);
                r.push(val);
            }
        }
        return r;
    }
    
    var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
    console.log(uniq(aa));
    
  • ES6
// ES6
function unique (arr) {
  const seen = new Map()
  return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}

const let var 区别?

  • var声明的变量会挂载在window上,而let和const声明的变量不会
  • var声明变量存在变量提升,let和const不存在变量提升
  • let和const声明形成块作用域
  • 同一作用域下let和const不能声明同名变量,而var可以
  • let和const有着暂存死区(即从作用域开始到变量申明的这一部分,不能使用该变量,否则会报错。)
  • const(一旦声明必须赋值,不能使用null占位)

图片预加载方式?

图片预加载的主要思路: 图片预加载的主要思路就是把稍后需要用到的图片悄悄的提前加载到本地,因为浏览器有缓存的原因,如果稍后用到这个url的图片了,浏览器会优先从本地缓存找该url对应的图片,如果图片没过期的话,就使用这个图片。

图片预加载实现的方法:

  • 适用js的image对象也有onload和onerror事件,分别是加载完后和加载失败时执行。

Image对象是专门用于处理图片加载的,就相当于内存中的img标签。

  • css实现图片预加载:写一个CSS样式设置一批背景图片,然后将其隐藏,这样你就看不到那些图片了。那些背景图片就是你想预载的图片
  • 使用Ajax实现预加载:该方法利用DOM,不仅仅预加载图片,还会预加载CSS、JavaScript等相关的东西。使用Ajax,比直接使用JavaScript,优越之处在于JavaScript和CSS的加载不会影响到当前页面。该方法简洁、高效。
  • 使用jQuery图片预加载(延迟加载)插件Lazy Load:Lazy Load也叫惰性加载,延迟加载,顾名思义,就是在图片未到达可视区域时,不加载图片

参考: 图片预加载的方法

在body中插入“<ul><li>第一行...</li><li>...</li>...</ul>”,ul中有10个li,要求插入时考虑性能

页面插入DOM会引起回流和重绘。尽量减少回流和重绘的次数为主要思路。

参考: http://developer.51cto.com/ar...

字符串的截取两个方法(subString subStr)的区别?

stringObject.substr(start,length):在字符串中抽取从 start 下标开始的指定数目的字符
stringObject.substring(start,stop): 用于提取字符串中介于两个指定下标之间的字符

谈谈CommonJs、AMD、CMD、ES6 Module?

CommonJs: 模块的加载方式是同步的(服务器端模块加载)
AMD: 异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。(浏览器端模块加载) AMD的方式比较适合浏览器(require.js 遵循的是AMD规范实现的模块加载)
CMD: CMD相当于按需加载,定义一个模块的时候不需要立即制定依赖模块,在需要的时候require就可以了,比较方便;而AMD则相反,定义模块的时候需要制定依赖模块,并以形参的方式引入factory中(SeaJS)

AMD 与 CMD 的区别:

  • AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
  • CMD推崇就近依赖,只有在用到某个模块的时候再去require

同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行

CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的

这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因

require.js 遵循AMD规范,主要为了解决下面两个问题:

  • 实现js文件的异步加载,避免网页失去响应
  • 管理模块之间的依赖性,便于代码的编写和维护

CommonJS的特点:

  • 所有代码都运行在模块作用域,不会污染全局作用域;
  • 模块是同步加载的,即只有加载完成,才能执行后面的操作;
  • 模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存;
  • require返回的值是被输出的值的拷贝,模块内部的变化也不会影响这个值。

ES6 Module
ES6 Module是ES6中规定的模块体系,相比上面提到的规范, ES6 Module有更多的优势,有望成为浏览器和服务器通用的模块解决方案。

ES6 Module的特点(对比CommonJS)

  • CommonJS模块是运行时加载,ES6 Module是编译时输出接口;
  • CommonJS加载的是整个模块,将所有的接口全部加载进来,ES6 Module可以单独加载其中的某个接口;
  • CommonJS输出是值的拷贝,ES6 Module输出的是值的引用,被输出模块的内部的改变会影响引用的改变;
  • CommonJS this指向当前模块,ES6 Module this指向undefined;

目前浏览器对ES6 Module兼容还不太好,我们平时在webpack中使用的export/import,会经过babel转换为CommonJS规范。

参考: http://www.ruanyifeng.com/blo... https://www.jianshu.com/p/042...
参考:再次梳理AMD、CMD、CommonJS、ES6 Module的区别

前端工程化的优点?

一切能提升前端开发效率,提高前端应用质量的手段和工具都是前端工程化

前端工程化可以分为4个方面:规范化,自动化,模块化,组件化
一:规范化

  • 目录结构的制定
  • 编码规范
  • 前后端接口规范
  • 文档规范
  • 协作工具,开发工具
  • 组件管理
  • Git分支管理
  • Commit描述规范
  • 定期CodeReview
  • 视觉图标规范

二:自动化

  • 自动图标合并,涉及到css sprite,svg sprite,图标字体
  • 自动编写可视化文档,技术选型:postmark+jsdoc
  • 自动化测试,技术选型:Karma + Mocha + Expect.js
  • 自动化部署,技术选型:docker
  • 自动化问题反馈

三:模块化
模块化就是将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。只有这样,才有多人协作的可能。

  • js的模块化
  • css的模块化
  • 资源的模块化

四:组件化:
优点:组件之间可以隔离,可以很好的降低复杂度,隐藏性更好,高内聚,低耦合。

事件循环?

主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去任务队列中取下一个任务执行。

JavaScript中有两种异步任务:
  1. 宏任务: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
  2. 微任务: process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;

JavaScript的运行机制:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步

概括即是: 调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作

需要注意的是:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。

怎么检测数组?

  • instanceof
  • isArray
  • Object.toString

注意: typeof 数组和Null 返回的是'Object'

0.1+0.2 等于多少?

实数有无数个,但javascript通过浮点数形式只能表示其中有限的个数(确切说是18 437 736 874 454 810 627个)。也就是说,当在javascript使用实数的时候,常常只是真实值的一个近似表示。
  javascript采用了IEEE-754浮点表示,这是一种二进制表示法,可以精确表示分数,比如1/2,1/8,1/1024。二进制浮点数表示法并不能精确表示类似0.1这样简单的数字。

参考

将对象的属性变为私有属性?

var obj = {
    x: 1,
    speak(){}
}
将c的x属性变为私有属性

参考:ES6 Class中实现私有属性的一些方法总结

如果某一个接口很缓慢,用户离开这个页面该怎么处理?

一个字符串,找没有重复过的字符串?

不知道IP地址怎么统计UV?

实际过程中有没有对 iview 进行过封装?


我们不动
794 声望44 粉丝

知耻而后勇